#region File Header
//
// CRC32.cs - Computes a 32-bit CRC 
//
// Copyright (C) Javier Valcarce. BSD License
#endregion

#region Using Statements
using System;
using System.Diagnostics;
using System.IO;
using System.Collections.Generic;
using System.Text;
#endregion

namespace Arq
{
  /// <summary>
  /// Cyclic code redundancy calculator for any 32-bit generator polynomial.
  /// 
  /// Convention: Remaindet initialized with InitValue
  /// Convention: The first serial data bit is the byte's MSB
  /// </summary>
  public class Crc32 
  {
    uint polinomial;
    uint crc;
    int  count;

    public const uint InitValue = 0xFFFFFFFF;

    static uint[] crctable;


    //static void Main(string[] args)
    //{
    //    if (!File.Exists(args[0]))
    //    {
    //        Console.WriteLine("No such file");
    //        return;
    //    }

    //    FileStream ifile = new FileStream(args[0], FileMode.Open);

    //    byte[] b = new byte[ifile.Length];
    //    ifile.Read(b, 0, b.Length);
    //    Crc16 crc16 = new Crc16(0x8005);

    //    Console.WriteLine("Computing CRC-16...");            
    //    for (int i = 0; i < 6; i++)
    //    {
    //        crc16.Compute(0xff);
    //        Console.WriteLine("Feed 0xFF -> 0x{0:X4}", crc16.Redundancy);
    //    }
    //    crc16.Reset();
    //    for (int i = 0; i < 6; i++)
    //    {
    //        crc16.Compute(0x01);
    //        Console.WriteLine("Feed 0x01 -> 0x{0:X4}", crc16.Redundancy);
    //    }
    //    //crc16.Compute(b, 0, b.Length);
    //    //Console.WriteLine("0x{0:X4}", crc16.Redundancy);

    //    Console.WriteLine("{0} bytes computed", crc16.Count);
    //}

    /// <summary>
    /// Constructor
    /// </summary>
    /// <param name="polinomial">Generator polynomial in hexadecimal notation. For example: the 
    /// polinomial g(x) = 1 + x2 + x15 + x16 has a hexadecimal representation 0x8005</param>
    public Crc32(uint polinomial)
    {
      Debug.Assert((polinomial & 0x0001) == 0x0001, 
		   "Bad polynomial, zero order coeficcient must be 1"); 

      this.polinomial = polinomial;
      // Initialized with InitValue
      crc = InitValue;
      count = 0;

      // table
      crctable = new uint[0x10000]; // 0xFFFF + 1

      uint t;
      uint c;

      for (int r = 0; r < 0x10000; r++)            
	{
	  c = (uint) r;

	  for (int j = 0; j < 8; j++)
	    {
	      t = (uint)(c & 0x8000);
	      c = (uint)(c << 1);

	      if (t == 0x8000)
		{
		  c = (uint)(c ^ polinomial);
		}
	    }

	  crctable[r] = c;
	}

      //for (int r = 0; r < 0x10000; r++)
      //{
      //    Console.WriteLine("{0,5} -> 0x{1:x4}", r, crctable[r]);
      //}
    }

    #region Sequential Compute CRC
    ///// <summary>
    ///// Compute/Update the CRC with a single byte
    ///// </summary>
    ///// <param name="b"></param>
    //public void SequentialCompute(byte b)
    //{
    //    uint s;
    //    uint t;

    //    s   = (uint)(b  << 8);
    //    crc = (uint)(crc ^ s);

    //    for (int j = 0; j < 8; j++)
    //    {
    //        t   = (uint)(crc & 0x8000);
    //        crc = (uint)(crc << 1);

    //        if (t == 0x8000)
    //        {
    //            crc = (uint)(crc ^ polinomial);
    //        }
    //    }
    //}

    ///// <summary>
    ///// Compute/Update the CRC with an array of bytes
    ///// </summary>
    ///// <param name="buf"></param>
    ///// <param name="offset"></param>
    ///// <param name="count"></param>
    //public void SequentialCompute(byte[] buf, int offset, int count)
    //{
    //    uint s;
    //    uint t;

    //    for (int i = 0; i < count; i++)
    //    {
    //        s = (uint)(buf[offset + i] << 8);
    //        crc = (uint)(crc ^ s);

    //        for (int j = 0; j < 8; j++)
    //        {
    //            t = (uint)(crc & 0x8000);
    //            crc = (uint)(crc << 1);

    //            if (t == 0x8000)
    //            {
    //                crc = (uint)(crc ^ polinomial);
    //            }
    //        }
    //    }

    //    this.count += count;
    //}
    #endregion

    #region Parallel computation of CRC
    public void Compute(byte[] buf, int offset, int count)
    {
      uint s;
           
      for (int i = 0; i < count; i++)
	{
	  s   = (uint)(buf[offset + i] << 8);
	  crc = (uint)(crc ^ s);

	  crc = crctable[crc];
	}
      this.count += count;
    }

    public void Compute(byte b)
    {
      uint s;

      s   = (uint)(b  << 8);
      crc = (uint)(crc ^ s);

      crc = crctable[crc];

      this.count++;
    }

    /// <summary>
    /// Computes a 16-bit CRC from frame[offset+0] to frame[offset+count-2] and store it at 
    /// frame[offset+count+1] and frame[offset+count+2], i.e, the last 2 bytes of the range
    /// </summary>
    /// <param name="frame">Frame</param>
    public void AppendCode(byte[] frame, int offset, int count)
    {
      uint localcrc = InitValue;
      uint s;

      for (int i = 0; i < count; i++)
	{
	  s        = (uint)(frame[offset + i] << 8);
	  localcrc = (uint)(localcrc           ^ s);
	  localcrc = crctable[localcrc];
	}

      byte lsb = (byte)((localcrc & 0x00FF) >> 0);
      byte msb = (byte)((localcrc & 0xFF00) >> 8);

      // little endian, lsb first
      frame[offset + count + 0] = lsb;
      frame[offset + count + 1] = msb;
    }


    /// <summary>
    /// Check a frame. Computes CRC from frame[0] to frame[frame.Length-3]. It is supposed that 
    /// then redundancy is in the last 2 bytes of the array. Doesn't affect the internal state 
    /// of the object (current crc remains the same)
    /// </summary>
    /// <param name="frame">Frame to be checked</param>
    /// <returns>True if valid frame, false if not</returns>
    public bool ValidFrame(byte[] frame, int offset, int count)
    {
      uint localcrc = InitValue;
      uint s;

      for (int i = 0; i < count; i++)
	{
	  s        = (uint)(frame[offset + i] << 8);
	  localcrc = (uint)(localcrc ^ s);
	  localcrc = crctable[localcrc];
	}

      byte lsb = (byte)((localcrc & 0x00FF) >> 0);
      byte msb = (byte)((localcrc & 0xFF00) >> 8);

      if (frame[offset + count + 0] != lsb) return false;
      if (frame[offset + count + 1] != msb) return false;

      return true;
    }

    /// <summary>
    /// Resets current CRC value
    /// </summary>
    public void Reset()
    {
      crc = InitValue;
      count = 0;
    }
    #endregion


    #region Properties
    /// <summary>
    /// Current CRC computed value
    /// </summary>
    public uint Code { get { return crc; } }

    /// <summary>
    /// Number of octets processed
    /// </summary>
    public int Count { get { return count; } }
    #endregion

  }
}
